﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver.Linq;
using MongoDB.Bson;
using MongoDB.Driver.Builders;
using MongoDB.Driver;
using YmtAuth.Domain;
using YmtAuth.Domain.Repository;
using YmtAuth.Repository.Register;
using YmtSystem.CrossCutting;

namespace YmtAuth.Repository.Mongodb
{
    public class Repository_v1<TEntity, TKey> : IRepository_v1<TEntity, TKey>
        where TEntity : IEntity<TKey>
    {
        private DbContext _context;

        public Repository_v1(DbContext context)
        {
            this._context = context;
        }
        protected DbContext Context { get { return _context; } }
        protected MongodbContext_NewCore NewCoreContext {
            get
            {
                if (_context.ContextNewCore == null) throw new NullReferenceException("MongodbContext_NewCore is null.");
                return _context.ContextNewCore;
            }
        }
        public EntityMappingConfigure MappingCfg { get { return EntityMappingSource.GetMapping(typeof(TEntity)); } }

        #region [ 不指定数据库表 ]
        public virtual TEntity FindById(TKey id)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).FindOneById(BsonValue.Create(id));
        }
        public virtual TEntity FindOne(Expression<Func<TEntity, bool>> exp)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).AsQueryable().Where(exp).FirstOrDefault();
        }
        public virtual TEntity FindAndModify(IMongoQuery query, IMongoUpdate update)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                .FindAndModify(query, SortBy.Null, update, true, true).GetModifiedDocumentAs<TEntity>();
        }
        public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> exp)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).AsQueryable().Where(exp);
        }
        public virtual IQueryable<TEntity> FindAll(string dbName, string collectionName, int index, int limit)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).FindAll().Skip(index * limit).Take(limit).AsQueryable();
        }
        public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> exp, int index, int limit)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                    .AsQueryable()
                    .Where(exp)
                    .Skip(index * limit)
                    .Take(limit);
        }

        public virtual IQueryable<TEntity> Find<TSortKey>(Expression<Func<TEntity, bool>> exp, Expression<Func<TEntity, TSortKey>> orderExp, System.Data.SqlClient.SortOrder order, int index, int limit)
        {
            var query = _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                                                                                                .AsQueryable()
                                                                                                .Where(exp);
            if (order == System.Data.SqlClient.SortOrder.Ascending)
                return query.OrderBy(orderExp)
                            .Skip(index * limit)
                            .Take(limit);
            else if (order == System.Data.SqlClient.SortOrder.Descending)
                return query.OrderByDescending(orderExp)
                           .Skip(index * limit)
                           .Take(limit);
            else
                return query.OrderByDescending(orderExp)
                           .Skip(index * limit)
                           .Take(limit);
        }
        public virtual void Add(TEntity entity)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Insert(entity);
        }

        public virtual void Save(TEntity entity)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Save(entity);
        }
        public virtual void Save(TEntity entity,WriteConcern w)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Save(entity,w);
        }
        public virtual void Save(TEntity entity, WriteConcern w, string dbName, string collectionName)
        {
            _context.GetTypeCollection<TEntity>(dbName, collectionName).Save(entity, w);
        }
        public virtual void Save(TEntity entity, string dbName, string collectionName)
        {
            _context.GetTypeCollection<TEntity>(dbName, collectionName).Save(entity);
        }

        public virtual void AddRang(IEnumerable<TEntity> entity)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).InsertBatch(entity);
        }

        public virtual void Remove(TKey id)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Remove(Query<TEntity>.EQ(e => e.Id, id));
        }

      
        public virtual bool Exists(Expression<Func<TEntity, bool>> exp)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).AsQueryable().FirstOrDefault(exp) != null;
        }

        public virtual long Count(Expression<Func<TEntity, bool>> exp)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                   .AsQueryable()
                   .Where(exp)
                   .Count();
        }

        #region   [ 基于mongo Query ]
        public virtual IQueryable<TEntity> Find(IMongoQuery query, IMongoFields fields)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Find(query).SetFields(fields).AsQueryable();
        }
        public virtual IQueryable<TEntity> Find(IMongoQuery query)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Find(query).AsQueryable();
        }
        public IQueryable<TEntity> Find(IMongoQuery query, string dbName, string tbName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, tbName).Find(query).AsQueryable();
        }
        public virtual TEntity FindOne(IMongoQuery query, bool isSingleOrDefault = false)
        {
            if (isSingleOrDefault)
            {
                return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Find(query).SingleOrDefault();
            }
            else
            {
                return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).FindOne(query);
            }
        }
        public virtual TEntity FindOne(IMongoQuery query, string dbName, string tbName, bool isSingleOrDefault = false)
        {
            if (isSingleOrDefault)
            {
                return _context.GetTypeCollection<TEntity>(dbName, tbName).Find(query).SingleOrDefault();
            }
            else
            {
                return _context.GetTypeCollection<TEntity>(dbName, tbName).FindOne(query);
            }
        }
        public virtual TEntity FindOneByPrimaryPreferred(IMongoQuery query, string dbName, string tbName, bool isSingleOrDefault = false)
        {
            var settings = new MongoCollectionSettings {ReadPreference = ReadPreference.PrimaryPreferred};
            if (isSingleOrDefault)
            {
                return _context.GetTypeCollection<TEntity>(dbName, tbName,settings).Find(query).SingleOrDefault();
            }
            else
            {
                return _context.GetTypeCollection<TEntity>(dbName, tbName, settings).FindOne(query);
            }
        }
        public virtual IQueryable<TEntity> Find(IMongoQuery query, IMongoFields fields, int index, int limit)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                        .Find(query)
                        .SetFields(fields)
                        .SetSkip(index * limit)
                        .SetLimit(limit)
                        .AsQueryable();
        }
        public virtual IQueryable<TEntity> Find<TSortKey>(IMongoQuery query, IMongoSortBy sort, IMongoFields fields, int index, int limit)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                    .Find(query)
                    .SetFields(fields)
                    .SetSortOrder(sort)
                    .SetSkip(index * limit)
                    .SetLimit(limit)
                    .AsQueryable();
        }
        public virtual bool Remove(IMongoQuery query)
        {
            return
                _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                    .Remove(query)
                    .DocumentsAffected > 0;
        }
        public virtual bool Exists(IMongoQuery query)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).FindOne(query) != null;
        }
        public virtual long Count(IMongoQuery query)
        {
            return _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection).Count(query);
        }
        public virtual void Update(IMongoQuery query, IMongoUpdate update)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                .Update(query, update, new MongoUpdateOptions { Flags = UpdateFlags.Upsert });
        }
        public virtual void Update(IMongoQuery query, IMongoUpdate update,WriteConcern w)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                .Update(query, update, new MongoUpdateOptions { Flags = UpdateFlags.Upsert,WriteConcern = w});
        }
        public virtual void Update(IMongoQuery query, IMongoUpdate update, WriteConcern w,string db,string tableName)
        {
            _context.GetTypeCollection<TEntity>(db, tableName)
                .Update(query, update, new MongoUpdateOptions { Flags = UpdateFlags.Upsert, WriteConcern = w });
        }
        public virtual void UpdateMulti(IMongoQuery query, IMongoUpdate update)
        {
            _context.GetTypeCollection<TEntity>(MappingCfg.ToDatabase, MappingCfg.ToCollection)
                .Update(query, update, new MongoUpdateOptions { Flags = UpdateFlags.Multi });
        }
       
        #endregion
       
        #endregion

        #region [ 指定数据库，表 ]
        public virtual TEntity FindById(TKey id, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).FindOneById(BsonValue.Create(id));
        }
        public virtual TEntity FindOne(Expression<Func<TEntity, bool>> exp, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).AsQueryable().FirstOrDefault(exp);
        }

        public virtual TEntity FindOne(IMongoQuery query, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).FindOne(query);
        }

        public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> exp, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).AsQueryable().Where(exp);
        }
        public virtual void Add(TEntity entity, string dbName, string collectionName)
        {
            _context.GetTypeCollection<TEntity>(dbName, collectionName).Insert(entity);
        }
        public virtual void Update(IMongoQuery query, IMongoUpdate update, string dbName, string collectionName)
        {
            _context.GetTypeCollection<TEntity>(dbName, collectionName)
                .Update(query, update, new MongoUpdateOptions { Flags = UpdateFlags.Upsert });
        }
       
        public virtual void AddRang(IEnumerable<TEntity> entity, string dbName, string collectionName)
        {
            _context.GetTypeCollection<TEntity>(dbName, collectionName).InsertBatch(entity);
        }

        public virtual bool Remove(IMongoQuery query, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).Remove(query).DocumentsAffected > 0;
        }

        public virtual bool Exists(Expression<Func<TEntity, bool>> exp, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).AsQueryable().FirstOrDefault(exp) != null;
        }

        public virtual long Count(Expression<Func<TEntity, bool>> exp, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).AsQueryable().LongCount(exp);
        }

        public virtual long Count(IMongoQuery query, string dbName, string collectionName)
        {
            return _context.GetTypeCollection<TEntity>(dbName, collectionName).Count(query);
        }

        #endregion
        #region [ async ]
        public virtual async Task AddAsync(TEntity entity, Action<AggregateException> errHandle = null
            , CancellationToken token = default(CancellationToken))
        {
            try
            {
                await NewCoreContext.Database(MappingCfg.ToDatabase).GetCollection<TEntity>(MappingCfg.ToCollection)
                   .InsertOneAsync(entity, cancellationToken: token).ConfigureAwait(false);               
            }
            catch (AggregateException ex)
            {
                if (ex != null)
                    errHandle(ex);
            }
            catch (Exception ex)
            {
                YmatouLoggingService.Error("AddAsync ex", ex);
            }
        }

        public virtual async Task AddAsync(TEntity entity, string dbName, string collectionName,
            Action<AggregateException> errHandle = null, CancellationToken token = default(CancellationToken))
        {
            try
            {
                await NewCoreContext.Database(dbName).GetCollection<TEntity>(collectionName)
                    .InsertOneAsync(entity, cancellationToken: token).ConfigureAwait(false);
            }
            catch
                (AggregateException ex)
            {
                if (ex != null)
                    errHandle(ex);
            }
            catch (Exception ex)
            {
                YmatouLoggingService.Error("AddAsync ex {0}", ex);
            }

        }

        public virtual async Task<DeleteResult> RemoveAsync(FilterDefinition<TEntity> queryfilter,CancellationToken token=default(CancellationToken))
        {
            return
                await
                        NewCoreContext
                        .Database(MappingCfg.ToDatabase)
                        .GetCollection<TEntity>(MappingCfg.ToCollection)
                        .DeleteOneAsync(queryfilter, token)
                        .ConfigureAwait(false);
            
        }
        #endregion
    }
}
